-- HEADER
local Capping = CreateFrame("Button", "Capping", UIParent)
local self = Capping
local L = CappingLocale

-- LIBRARIES
local media = LibStub("LibSharedMedia-3.0")
media:Register("statusbar", "BantoBarReverse", "Interface\\AddOns\\Capping\\textures\\BantoBarReverse")
media:Register("statusbar", "Steel", "Interface\\AddOns\\Capping\\textures\\Steel")

-- GLOBALS MADE LOCAL
local _G = getfenv(0)
local format, strfind, strlower, select, type = format, strfind, strlower, select, type
local min, max, floor, math_sin, math_pi, tonumber = min, max, floor, math.sin, math.pi, tonumber
local GetTime, time = GetTime, time
local GetWorldStateUIInfo = GetWorldStateUIInfo

-- LOCAL VARS
local db, f, wasInBG, bgmap, realm, pname
local activebars, bars, currentq, bgdd = { }, { }, { }, { }
local av, ab, eots, wsg, winter = L["Alterac Valley"], L["Arathi Basin"], L["Eye of the Storm"], L["Warsong Gulch"], L["Wintergrasp"]
local backdrop = { bgFile = "Interface\\Tooltips\\UI-Tooltip-Background", }
local narrowed, borderhidden, sbhook, cbhook, seathook, bgtabhook, pointset

-- LOCAL FUNCS
local ShowOptions
local oSetPoint = Capping.SetPoint
local function IsInBG()
	return select(2, IsInInstance()) == "pvp"
end
local function IsInArena()
	return select(2, IsInInstance()) == "arena"
end
local function ToggleAnchor()
	if f:IsShown() then
		f:Hide()
	else 
		f:Show() 
	end
end
local function InitBGMap()
	self:UnregisterEvent("ADDON_LOADED")
	self.ADDON_LOADED = nil
	bgmap = BattlefieldMinimap
	self:ModMap()
	bgdd.text, bgdd.func = "Capping", ShowOptions
	hooksecurefunc("BattlefieldMinimapDropDown_Initialize", function() UIDropDownMenu_AddButton(bgdd, 1) end)
	InitBGMap = nil
end
local function SetPoints(this, lp, lrt, lrp, lx, ly, rp, rrt, rrp, rx, ry)
	this:ClearAllPoints()
	this:SetPoint(lp, lrt, lrp, lx, ly)
	if rp then this:SetPoint(rp, rrt, rrp, rx, ry) end
end
local function SetWH(this, w, h)
	this:SetWidth(w)
	this:SetHeight(h)
end
local function NewText(parent, font, fontsize, justifyH, justifyV, overlay)
	local t = parent:CreateFontString(nil, overlay or "OVERLAY")
	if fontsize then
		t:SetFont(font, fontsize)
	else
		t:SetFontObject(font)
	end
	t:SetJustifyH(justifyH) 
	t:SetJustifyV(justifyV)
	return t
end
local function StartMoving(this) this:StartMoving() end
local function CreateMover(oldframe, w, h, dragstopfunc)
	local mover = oldframe or CreateFrame("Button", nil, UIParent)
	SetWH(mover, w, h)
	mover:SetBackdrop(backdrop)
	mover:SetBackdropColor(0, 0, 0, 0.7)
	mover:SetMovable(true)
	mover:RegisterForDrag("LeftButton")
	mover:SetScript("OnDragStart", StartMoving)
	mover:SetScript("OnDragStop", dragstopfunc)
	mover:SetClampedToScreen(true)
	mover:SetFrameStrata("HIGH")
	mover.close = CreateFrame("Button", nil, mover, "UIPanelCloseButton")
	SetWH(mover.close, 20, 20)
	mover.close:SetPoint("TOPRIGHT", 5, 5)
	return mover
end 
local function UpdateWorldInfoPosition()
	if not sbhook then
		hooksecurefunc(WorldStateAlwaysUpFrame, "SetPoint", UpdateWorldInfoPosition)
		sbhook = true
	end
	oSetPoint(WorldStateAlwaysUpFrame, "TOP", UIParent, "TOPLEFT", db.sbx, db.sby)
end
local function UpdateCaptureBarPosition()
	if not cbhook then
		hooksecurefunc("WorldStateAlwaysUpFrame_Update", UpdateCaptureBarPosition)
		cbhook = true
	end
	local nexty = 0
	for i=1, NUM_EXTENDED_UI_FRAMES do
		local cb = _G["WorldStateCaptureBar"..i]
		if cb and cb:IsShown() then
			cb:SetPoint("TOPRIGHT", UIParent, "BOTTOMLEFT", db.cbx, db.cby - nexty)
			nexty = nexty + cb:GetHeight()
		end
	end
end
local function UpdateVehicleSeatPosition()
	if not seathook then
		hooksecurefunc(VehicleSeatIndicator, "SetPoint", UpdateVehicleSeatPosition)
		seathook = true
	end
	oSetPoint(VehicleSeatIndicator, "TOPRIGHT", UIParent, "BOTTOMLEFT", db.seatx, db.seaty)
end


-- EVENT HANDLERS
local elist, clist = {}, {}
Capping:SetScript("OnEvent", function(this, event, ...)
	this[elist[event] or event](this, ...)
end)
function Capping:RegisterTempEvent(event, other)
	self:RegisterEvent(event)
	elist[event] = other or event
end
function Capping:CheckCombat(func)
	if InCombatLockdown() then
		self:RegisterEvent("PLAYER_REGEN_ENABLED")
		tinsert(clist, func)
	else
		func(self)
	end
end
function Capping:PLAYER_REGEN_ENABLED()
	for k,v in ipairs(clist) do
		v(self)
		clist[k] = nil
	end
	self:UnregisterEvent("PLAYER_REGEN_ENABLED")
end


Capping:RegisterEvent("ADDON_LOADED")
---------------------------------
function Capping:ADDON_LOADED(a1)
---------------------------------
	if a1 ~= "Capping" then return end
	
	-- saved variables database setup
	CappingDB = CappingDB or {}
	db = (CappingDB.profiles and CappingDB.profiles.Default) or CappingDB
	self.db = db
	if db.dbinit ~= 5 then 
		db.dbinit = 5
		local function SetDefaults(db, t)
			for k,v in pairs(t) do
				if type(db[k]) == "table" then
					SetDefaults(db[k], v)
				else
					db[k] = (db[k] ~= nil and db[k]) or v
				end
			end
		end
		SetDefaults(db, {
			av = true, avquest = true, ab = true, wsg = true, arena = true, eots = true, winter = true,
			port = true, wait = true,
			mapscale = 1.3, narrow = true, hidemapborder = false,
			texture = "BantoBarReverse",
			width = 200, height = 15,
			inset = 1, spacing = 1,
			mainup = false, reverse = false, fill = false,
			iconpos = L["LEFT"], timepos = L["LEFT"],
			font = "Friz Quadrata TT", fontsize = 11,
			colors = {
				alliance = { r=0.0, g=0.0, b=1.0, a=1.0, },
				horde = { r=1.0, g=0.0, b=0.0, a=1.0, },
				info1 = { r=0.6, g=0.6, b=0.6, a=1.0, },
				info2 = { r=1.0, g=1.0, b=0.0, a=1.0, },
				bg = { r=0.3, g=0.3, b=0.3, a=0.5, },
				font = { r=1, g=1, b=1, a=1, },
			},
		})
	end
  	SlashCmdList.CAPPING = ShowOptions
   	SLASH_CAPPING1 = "/capping"
   	
   	realm = GetCVar("realmName")
	pname = UnitName("player")

   	-- adds Capping config to default UI Interface Options
	local panel = CreateFrame("Frame")
	panel.name = "Capping"
	panel:SetScript("OnShow", function(this)
		local t1 = NewText(this, GameFontNormalLarge, nil, "LEFT", "TOP", "ARTWORK")
		t1:SetPoint("TOPLEFT", 16, -16)
		t1:SetText(this.name)
		
		local t2 = NewText(this, GameFontHighlightSmall, nil, "LEFT", "TOP", "ARTWORK")
		t2:SetHeight(43)
		SetPoints(t2, "TOPLEFT", t1, "BOTTOMLEFT", 0, -8, "RIGHT", this, "RIGHT", -32, 0)
		t2:SetNonSpaceWrap(true)
		local function GetInfo(field)
			return GetAddOnMetadata("Capping", field) or "N/A"
		end
		t2:SetFormattedText("Notes: %s\nAuthor: %s\nVersion: %s", GetInfo("Notes"), GetInfo("Author"), GetInfo("Version"))
	
		local b = CreateFrame("Button", nil, this, "UIPanelButtonTemplate")
		SetWH(b, 120, 20)
		b:SetText("Options Menu")
		b:SetScript("OnClick", ShowOptions)
		b:SetPoint("TOPLEFT", t2, "BOTTOMLEFT", -2, -8)
		this:SetScript("OnShow", nil)
	end)
	InterfaceOptions_AddCategory(panel)
	
	-- anchor frame
	f = self
	f:Hide()
	if db.x then
		f:SetPoint(db.p or "TOPLEFT", UIParent, db.rp or "TOPLEFT", db.x, db.y)	
	else
		f:SetPoint("CENTER", UIParent, "CENTER", 200, -100)
	end
	CreateMover(f, db.width, 10, function(this) 
		this:StopMovingOrSizing()
		local a,b,c,d,e = this:GetPoint()
		db.p, db.rp, db.x, db.y = a, c, floor(d + 0.5), floor(e + 0.5)
	end)
	f:RegisterForClicks("RightButtonUp")
	f:SetScript("OnClick", ShowOptions)
	f:SetNormalFontObject(GameFontHighlightSmall)
	f:SetText("Capping")

	if db.sbx then UpdateWorldInfoPosition() end  -- world state info frame positioning
	if db.cbx then UpdateCaptureBarPosition() end  -- capturebar position
	if db.seatx then UpdateVehicleSeatPosition() end  -- vehicle seat position
	
	self:RegisterEvent("PLAYER_ENTERING_WORLD")
	self:RegisterEvent("UPDATE_BATTLEFIELD_STATUS")
	
	if BattlefieldMinimap then  -- battlefield minimap setup
		InitBGMap()
	else
		self:RegisterEvent("ADDON_LOADED")
		function Capping:ADDON_LOADED(a1)
			if a1 == "Blizzard_BattlefieldMinimap" then
				InitBGMap()
			end
		end
	end
end

local AddonBroadcast, StartWinterGraspTimer
do  -- Wintergrasp timer handling and syncing
	AddonBroadcast = function(msg)
		if IsInGuild() then 
			SendAddonMessage("WGTime", msg, "GUILD")
		end
		if UnitExists("raid1") then
			SendAddonMessage("WGTime", msg, "RAID")
		elseif UnitExists("party1") then 
			SendAddonMessage("WGTime", msg, "PARTY") 
		end
	end
	local lastsync = 0
	StartWinterGraspTimer = function(duration, skipsync)
		duration = duration * 1.025
		db[realm] = time() + duration
		Capping:StartBar(winter, (duration > 950 and 9225) or 923, duration, "Interface\\Icons\\Ability_Mage_ShatterShield", "info1", 1500, true)
		if not skipsync and lastsync + 10 < GetTime() then
			AddonBroadcast(duration)
			lastsync = GetTime()
		end
	end
	local WinterSync = CreateFrame("Frame", nil, Capping)
	WinterSync:RegisterEvent("CHAT_MSG_ADDON")
	WinterSync:SetScript("OnEvent", function(this, event, prefix, message, chan, sender)
		if prefix ~= "WGTime" or sender == pname then return end
		if message == "timerequest" then
			local remaining = (db[realm] or 0) - time()
			if remaining > 5 then
				SendAddonMessage("WGTime", remaining, chan)
			end
		else
			local tm = tonumber(message)
			if tm and tm > 5 and tm < 10000 then
				StartWinterGraspTimer(tm, true)
			end
		end
	end)
end

----------------------------------------
function Capping:PLAYER_ENTERING_WORLD()
----------------------------------------
	self:UnregisterEvent("PLAYER_ENTERING_WORLD")
	self:RegisterEvent("ZONE_CHANGED_NEW_AREA")
	self:UPDATE_BATTLEFIELD_STATUS()
	self:ZoneCheck()
	if db.winter then
		if db[realm] and time() < db[realm] then
			self:StartBar(winter, 9225, db[realm] - time(), "Interface\\Icons\\Ability_Mage_ShatterShield", "info1", 1500, true)
		else
			AddonBroadcast("timerequest")
		end
	else
		db[realm] = nil
	end
	self.PLAYER_ENTERING_WORLD = nil
end

----------------------------------------
function Capping:ZONE_CHANGED_NEW_AREA()
----------------------------------------
	if wasInBG then
		self:ResetAll()
	end
	self:ZoneCheck()
end


local tohide = { }
local function HideProtectedStuff()
	for _,v in ipairs(tohide) do
		v:Hide()
	end
end
--------------------------------------
function Capping:AddFrameToHide(frame)  -- secure frames that are hidden upon zone change
--------------------------------------
	tinsert(tohide, frame)
end
---------------------------
function Capping:ResetAll()  -- reset all timers and unregister temp events
---------------------------
	wasInBG = false
	for event in pairs(elist) do  -- unregister all temp events
		elist[event] = nil
		self:UnregisterEvent(event)
	end
	for value,_ in pairs(activebars) do  -- close all temp timerbars
		self:StopBar(value)
		activebars[value] = nil
	end
	self:CheckCombat(HideProtectedStuff)  -- hide secure frames
end

local wscheck = 0
----------------------------
function Capping:ZoneCheck()  -- check if new zone is a battleground
----------------------------
	if IsInBG() then
		local z = GetRealZoneText()
		wasInBG = true
		if z == wsg and db.wsg then
			self:StartWSG()
		elseif z == eots and db.eots then
			self:StartEotS()
		elseif z == ab and db.ab then
			self:StartAB()
		elseif z == av and db.av then
			self:StartAV()
		else
			self:RegisterTempEvent("CHAT_MSG_BG_SYSTEM_NEUTRAL", "CheckStartTimer")
		end
		if (not bgmap or not bgmap:IsShown()) and GetCVar("showBattlefieldMinimap") ~= "0" then
			if not bgmap then
				LoadAddOn("Blizzard_BattlefieldMinimap")
			end
			bgmap:Show()
		end
	elseif GetMapInfo() == "LakeWintergrasp" then
		if (not bgmap or not bgmap:IsShown()) and GetCVar("showBattlefieldMinimap") ~= "0" then
			if not bgmap then
				LoadAddOn("Blizzard_BattlefieldMinimap")
			end
			bgmap:Show()
		end
		if db.winter then
			Capping.WintergraspTimerZone = Capping.WintergraspTimerZone or function()
				if GetTime() < wscheck + 2 then return end
				local h, m, s
				local _, inprog, progresstext = GetWorldStateUIInfo(5)
				if inprog == 1 then
					m, s = strmatch(progresstext or "blah", "(%d+):(%d+)")
				else
					local _, inwait, waitingtext = GetWorldStateUIInfo(8)
					if inwait == 1 then
						h, m, s = strmatch(waitingtext or "blah", "(%d+):(%d+):(%d+)")
					end
				end
				if not m or not s then return end
				h, m, s = tonumber(h or 0), tonumber(m), tonumber(s)
				if h < 4 and m < 60 and s > 5 and s < 50 then
					local duration = h * 3600 + m * 60 + s
					if inprog == 1 then
						Capping:StartBar(winter, 1800, duration, "Interface\\Icons\\Ability_Mage_ShatterShield", "info1")
					elseif duration > 10 then
						StartWinterGraspTimer(duration)
					end
					wscheck = GetTime() + (((duration < 20) and 30) or 0)
				end
			end
			self:RegisterTempEvent("UPDATE_WORLD_STATES", "WintergraspTimerZone")
			wasInBG = true
		end
	else
		if IsInArena() then
			self:RegisterTempEvent("CHAT_MSG_BG_SYSTEM_NEUTRAL", "CheckStartTimer")
			wasInBG = true
		elseif db.winter and GetMapInfo() == "Dalaran" then
			Capping.WintergraspTimerGossip = Capping.WintergraspTimerGossip or function()
				local h, m, s = strmatch(GetGossipText(), "(%d+):(%d+):(%d+)")
				if h and m and s then
					StartWinterGraspTimer(tonumber(h) * 3600 + tonumber(m) * 60 + tonumber(s))
				end
			end
			Capping.WintergraspTimerYell = Capping.WintergraspTimerYell or function(_, a1)
				if strfind(a1, winter) then
					if strfind(a1, "15") then
						StartWinterGraspTimer(900)
					elseif strfind(a1, "5") then
						StartWinterGraspTimer(300)
					end
				end
			end
			self:RegisterTempEvent("GOSSIP_SHOW", "WintergraspTimerGossip")
			self:RegisterTempEvent("CHAT_MSG_MONSTER_YELL", "WintergraspTimerYell")
			wasInBG = true
		end
		if bgmap and bgmap:IsShown() and GetCVar("showBattlefieldMinimap") ~= "2" then
			bgmap:Hide()
		end
	end
	self:ModMap()
end

--------------------------------
function Capping:ModMap(disable)  -- alter the default minimap
--------------------------------
	if not bgmap then return end
	bgmap:SetScale(db.mapscale)
	disable = not IsInBG() or disable
	
	if db.narrow and not narrowed and not disable then  -- narrow setting
		BattlefieldMinimap1:Hide()
		BattlefieldMinimap4:Hide()
		BattlefieldMinimap5:Hide()
		BattlefieldMinimap8:Hide()
		BattlefieldMinimap9:Hide()
		BattlefieldMinimap12:Hide()
		BattlefieldMinimapBackground:SetWidth(256 / 2)
		BattlefieldMinimapBackground:SetPoint("TOPLEFT", -12 + 64, 12)
		BattlefieldMinimapCorner:SetPoint("TOPRIGHT", -2 - 52, 3 + 1)
		SetWH(BattlefieldMinimapCorner, 24, 24)
		BattlefieldMinimapCloseButton:SetPoint("TOPRIGHT", bgmap, "TOPRIGHT", 2 - 53, 7)
		SetWH(BattlefieldMinimapCloseButton, 24, 24)
		narrowed = 1
	elseif disable or (not db.narrow and narrowed) then  -- setting things back to blizz's default
		BattlefieldMinimap1:Show()
		BattlefieldMinimap4:Show()
		BattlefieldMinimap5:Show()
		BattlefieldMinimap8:Show()
		BattlefieldMinimap9:Show()
		BattlefieldMinimap12:Show()
		BattlefieldMinimapBackground:SetWidth(256)
		BattlefieldMinimapBackground:SetPoint("TOPLEFT", -12, 12)
		BattlefieldMinimapCorner:SetPoint("TOPRIGHT", -2, 3)
		SetWH(BattlefieldMinimapCorner, 32, 32)
		BattlefieldMinimapCloseButton:SetPoint("TOPRIGHT", bgmap, "TOPRIGHT", 2, 7)
		SetWH(BattlefieldMinimapCloseButton, 32, 32)
		narrowed = nil
	end
	
	if narrowed and not db.narrowanchorleft and not pointset then  -- set anchoring after map narrowing
		if not bgtabhook then
			bgtabhook = BattlefieldMinimapTab.GetCenter
			BattlefieldMinimapTab.GetCenter = function(this)  -- make sure to return the right position for the saved variables
				local x, y = bgtabhook(this)
				if narrowed and pointset then
					return x - 128 - 25, y
				else
					return x, y
				end
			end
		end
		local tx, ty = bgtabhook(BattlefieldMinimapTab)
		if tx then
			BattlefieldMinimapTab:ClearAllPoints()
			BattlefieldMinimapTab:SetPoint("CENTER", UIParent, "BOTTOMLEFT", tx + (116 * db.mapscale), ty)
			BattlefieldMinimapTab:SetUserPlaced(true)
			pointset = true
		end
	elseif not narrowed and pointset then
		local tx, ty = bgtabhook(BattlefieldMinimapTab)
		BattlefieldMinimapTab:ClearAllPoints()
		BattlefieldMinimapTab:SetPoint("CENTER", UIParent, "BOTTOMLEFT", tx - (116 * db.mapscale), ty)
		pointset = nil
	end

	if db.hidemapborder and not borderhidden then  -- Hide border
		BattlefieldMinimapBackground:Hide()
		BattlefieldMinimapCorner:Hide()
		BattlefieldMinimapCloseButton:SetParent(BattlefieldMinimapTab)
		BattlefieldMinimapCloseButton:SetScale(db.mapscale)
		BattlefieldMinimapCloseButton:HookScript("OnClick", function() bgmap:Hide() end)
		borderhidden = true
	elseif not db.hidemapborder and borderhidden then  -- Show border
		BattlefieldMinimapBackground:Show()
		BattlefieldMinimapCorner:Show()
		BattlefieldMinimapCloseButton:SetParent(bgmap)
		BattlefieldMinimapCloseButton:SetScale(1)
		borderhidden = nil
	end
	bgmap:SetPoint("TOPLEFT", BattlefieldMinimapTab, "BOTTOMLEFT", (narrowed and -64) or 0, (borderhidden and 0) or -5)
end

do  -- estimated wait timer and the 2 min timer to port to a certain battleground
	local q, p = L["Queue: %s"], L["Port: %s"]
	local maxq = MAX_BATTLEFIELD_QUEUES
	local GetBattlefieldStatus = GetBattlefieldStatus
	local GetBattlefieldPortExpiration = GetBattlefieldPortExpiration
	local GetBattlefieldEstimatedWaitTime, GetBattlefieldTimeWaited = GetBattlefieldEstimatedWaitTime, GetBattlefieldTimeWaited
	--------------------------------------------
	function Capping:UPDATE_BATTLEFIELD_STATUS()
	--------------------------------------------
		if not db.port and not db.wait then return end
		
		for map in pairs(currentq) do  -- tag each entry to see if it's changed after updating the list
			currentq[map] = 0
		end
		for i=1,maxq,1 do  -- check the status of each queue
			local status, map, _, _, _, teamsize, _ = GetBattlefieldStatus(i)
			if map and teamsize and teamsize > 0 then -- arena
				map = format("%s (%dv%d)", map, teamsize, teamsize)
			end
			if status == "confirm" and db.port then
				self:StopBar(format(q, map))
				self:StartBar(format(p, map), 80, GetBattlefieldPortExpiration(i)/1000, "Interface\\Icons\\Ability_TownWatch", "info2", true, true)
				currentq[map] = i
			elseif status == "queued" and db.wait then
				local esttime, waitedtime = GetBattlefieldEstimatedWaitTime(i)/1000, GetBattlefieldTimeWaited(i)/1000
				self:StartBar(format(q, map), max(1, esttime), max(1, esttime-waitedtime), "Interface\\Icons\\INV_Misc_Note_03", "info1", 1500, true)
				currentq[map] = i
			end
		end
		for map,flag in pairs(currentq) do  -- stop inactive bars
			if flag == 0 then
				self:StopBar(format(p, map))
				self:StopBar(format(q, map))
				currentq[map] = nil
			end
		end
	end
end


local temp = { }
local function CheckActive(barid)
	local f = bars[barid]
	if f and f:IsShown() then return f end
end
local function DoReport(this, chan)  -- format chat reports
	if not activebars[this.name] then return end
	local faction = (this.color == "horde" and L["Horde"]) or (this.color == "alliance" and L["Alliance"]) or ""
	SendChatMessage(format(L["%s: %s - %d:%02d"], faction, this.displaytext:GetText(), this.remaining / 60, mod(this.remaining, 60)), chan)
end
local function CheckQueue(name, join)
	local qid = currentq[gsub(name, "^(.+): ", "")]
	if qid and type(qid) == "number" then
		AcceptBattlefieldPort(qid, join) -- leave queue
		return true
	end
end
local function ReportBG(barid)  -- report timer to /bg
	local this = CheckActive(barid)
	if not this then return end
	if not CheckQueue(this.name, true) then
		DoReport(this, "BATTLEGROUND")
	end
end
local function ReportSAY(barid)  -- report timer to /s
	local this = CheckActive(barid)
	if not this then return end
	DoReport(this, "SAY")
end
local function CancelBar(barid)  -- close bar, leave queue if a queue timer
	local this = CheckActive(barid)
	if not this then return end
	if not CheckQueue(this.name, nil) then
		self:StopBar(nil, this)
	end
end
local function BarOnClick(this, button)
	if button == "LeftButton" then
		if IsShiftKeyDown() then
			ReportSAY(this.id)
		elseif IsControlKeyDown() then
			ReportBG(this.id)
		else
			ToggleAnchor()
		end
	elseif button == "RightButton" then
		if IsControlKeyDown() then
			CancelBar(this.id)
		else
			ShowOptions(nil, this.id)
		end
	end
end
local function BarOnUpdate(this, a1)
	this.elapsed = this.elapsed + a1
	if this.elapsed < this.throt then return end
	this.elapsed = 0

	local remain = this.endtime - GetTime()
	this.remaining = remain
	
	this.bar:SetValue( ((db.fill and min(this.duration, this.duration - remain)) or max(0.0001, remain)) / this.duration )
	if remain < 60 then
		if remain < 10 then  -- fade effects
			if remain > 0.5 then
				this:SetAlpha(0.75 + 0.25 * math_sin(remain * math_pi))
			elseif remain > -1.5 then
				this:SetAlpha((remain + 1.5) / 2)
			elseif this.noclose then
				if remain < -this.noclose then
					self:StopBar(nil, this)
				else
					this:SetAlpha(0.7)
					this.throt = 10
				end
				return
			else
				self:StopBar(nil, this)
				return
			end
			this.throt = 0.05
		end
		this.timetext:SetFormattedText("%d", max(0, remain))
	elseif remain < 600 then
		this.timetext:SetFormattedText("%d:%02d", remain/60, mod(remain,60))
	elseif remain < 3600 then
		this.timetext:SetFormattedText("%dm", remain/60)
	else
		this.timetext:SetFormattedText("%0.1fh", remain/3600)
	end
end
local function lsort(a, b)
	return bars[a].remaining < bars[b].remaining
end
local function SortBars()
	for i = 1, getn(bars), 1 do
		temp[i] = i
	end
	sort(temp, lsort)
	local pdown, pup = -1, -1
	for _, k in ipairs(temp) do
		local f = bars[k]
		if f:IsShown() then
			if f.down then
				SetPoints(f, "TOPLEFT", bars[pdown] or Capping, "BOTTOMLEFT", 0, -(db.spacing or 1))
				pdown = k
			else
				SetPoints(f, "BOTTOMLEFT", bars[pup] or Capping, "TOPLEFT", 0, db.spacing or 1)
				pup = k
			end
		end
	end
end
local function SetValue(this, frac)
	this:SetWidth(frac * this.basevalue)
	this:SetTexCoord(0, frac, 0, 1)
end
local function SetReverseValue(this, frac)
	this:SetWidth(frac * this.basevalue)
	this:SetTexCoord(frac, 0, 0, 1)
end
local function UpdateBarLayout(f)
	local inset, w, h = db.inset, db.width, db.height
	local tc = db.colors.font
	local icon, bar, barback, spark, timetext, displaytext = f.icon, f.bar, f.barback, f.spark, f.timetext, f.displaytext
	SetWH(f, w, h)
	SetWH(icon, h, h)
	SetWH(barback, w - h, h)
	bar:SetHeight(h - 2*inset)
	spark:SetHeight(2.1*(h - 2*inset))
	timetext:SetTextColor(tc.r, tc.g, tc.b, tc.a)
	displaytext:SetTextColor(tc.r, tc.g, tc.b, tc.a)
	if db.iconpos == "HIDE" then  -- icon position
		icon:Hide()
		barback:SetWidth(w)
		SetPoints(barback, "LEFT", f, "LEFT", 0, 0)
	elseif db.iconpos == "RIGHT" then
		icon:Show()
		SetPoints(icon, "RIGHT", f, "RIGHT", 0, 0)
		SetPoints(barback, "RIGHT", icon, "LEFT", 0, 0)
	else
		icon:Show()
		SetPoints(icon, "LEFT", f, "LEFT", 0, 0)
		SetPoints(barback, "LEFT", icon, "RIGHT", 0, 0)
	end
	if db.timepos == "RIGHT" then  -- time text placement
		SetPoints(timetext, "RIGHT", barback, "RIGHT", -(4 + inset), 0)
		SetPoints(displaytext, "LEFT", barback, "LEFT", (4 + inset), 0, "RIGHT", timetext, "LEFT", -(4 + inset), 0)
	else
		SetPoints(displaytext, "LEFT", barback, "LEFT", db.fontsize*3, 0, "RIGHT", barback, "RIGHT", -(4 + inset), 0)
		SetPoints(timetext, "RIGHT", displaytext, "LEFT", -db.fontsize/2, 0)
	end
	if db.reverse then  -- horizontal flip of bar growth
		SetPoints(bar, "RIGHT", barback, "RIGHT", -inset, 0)
		spark:SetPoint("CENTER", bar, "LEFT", 0, 0)
		bar.SetValue = SetReverseValue
	else
		SetPoints(bar, "LEFT", barback, "LEFT", inset, 0)
		spark:SetPoint("CENTER", bar, "RIGHT", 0, 0)
		bar.SetValue = SetValue
	end
	bar.basevalue = (w - h) - (2 * inset)
	if f:IsShown() then
		BarOnUpdate(f, 11)
	end
end
-------------------------------------
function Capping:GetBar(name, getone)  -- get active bar or unused/new one
-------------------------------------
	local f, tf
	for k, b in ipairs(bars) do  -- find already assigned bar
		if b.name == name then
			f = b
		elseif getone and not tf and not b:IsShown() then
			tf = b
		end
	end
	if not getone then return f end -- don't assign a new bar

	f = f or tf
	if not f then  -- no available bar, create new one
		local bgc = db.colors.bg
		local texture = media:Fetch("statusbar", db.texture)
		local font = media:Fetch("font", db.font)
		
		f = CreateFrame("Button", nil, UIParent)
		f:Hide()
		f:RegisterForClicks("LeftButtonUp", "RightButtonUp")
		f:SetScript("OnClick", BarOnClick)
		f:SetScript("OnUpdate", BarOnUpdate)
		f.icon = f:CreateTexture(nil, "ARTWORK")
		f.barback = f:CreateTexture(nil, "BACKGROUND")
		f.barback:SetTexture(texture)
		f.barback:SetVertexColor(bgc.r, bgc.g, bgc.b, bgc.a or 0.5)
		f.bar = f:CreateTexture(nil, "ARTWORK")
		f.bar:SetTexture(texture)
		
		local spark = f:CreateTexture(nil, "OVERLAY")
		spark:SetTexture("Interface\\CastingBar\\UI-CastingBar-Spark")
		spark:SetBlendMode("ADD")
		spark:SetAlpha(0.8)
		spark:SetWidth(18)
		f.spark = spark
		
		f.timetext = NewText(f, font, db.fontsize-1, "RIGHT", "CENTER")
		f.displaytext = NewText(f, font, db.fontsize, "LEFT", "CENTER")
		
		UpdateBarLayout(f)
		tinsert(bars, f)
		f.id = getn(bars)
	end
	
	return f
end
-------------------------------------------------------------------------------------------------------
function Capping:StartBar(name, duration, remaining, icondata, color, nonactive, separate, specialText)
-------------------------------------------------------------------------------------------------------
	local icon, l, r, t, b
	if type(icondata) == "table" then
		icon, l, r, t, b = icondata.path, icondata.l, icondata.r, icondata.t, icondata.b
	else
		icon, l, r, t, b = icondata, 0.07, 0.93, 0.07, 0.93
	end
	local c = db.colors[color or "info1"] or db.colors.info1
	if db.onegroup then
		separate = db.mainup
	elseif db.mainup then 
		separate = not separate 
	end
	
	local f = self:GetBar(name, true)
	f.name = name
	f.color = color
	f.endtime = GetTime() + remaining
	f.duration = duration
	f.remaining = remaining
	f.down = not separate
	f.noclose = type(nonactive) == "number" and nonactive
	f.throt = (duration < 300 and 0.1) or (duration < 600 and 0.25) or 0.5
	f.elapsed = f.throt - 0.01
	
	f.displaytext:SetText(specialText or name)
	f.icon:SetTexture(icon)
	f.icon:SetTexCoord(l, r, t, b)
	f.bar:SetVertexColor(c.r, c.g, c.b, c.a or 0.9)
	f:SetAlpha(1)
	
	activebars[name] = (not nonactive and color) or nil
	f:Show()
	SortBars()
end
------------------------------------
function Capping:StopBar(name, this)
------------------------------------
	local f = this or self:GetBar(name)
	if f then
		f.name = ""
		f:Hide()
		SortBars()
	end
end


local poicons
-------------------------------------------
function Capping:GetIconData(faction, name)
-------------------------------------------
	poicons = poicons or { -- textures used on worldmap { al, ar, at, ab, hl, hr, ht, hb } alliance/horde
		symbol = { 0.875, 0.9375, 0.125, 0.1875, 0, 0.0625, 0.1875, 0.25, },
		flag = { 0.6875, 0.75, 0.125, 0.1875, 0.75, 0.8125, 0.125, 0.1875, },
		graveyard = { 0.25, 0.3125, 0, 0.0625, 0.875, 0.9375, 0, 0.0625, },
		tower = { 0.625, 0.5625, 0, 0.0625, 0.8125, 0.75, 0, 0.0625, },
		farm = { 0, 0.0625, 0.125, 0.1875, 0.125, 0.1875, 0.125, 0.1875, },
		blacksmith = { 0.6875, 0.75, 0.0625, 0.125, 0.8125, 0.875, 0.0625, 0.125, },
		mine = { 0.0625, 0.125, 0.0625, 0.125, 0.1875, 0.25, 0.0625, 0.125, },
		stables = { 0.3125, 0.375, 0.125, 0.1875, 0.4375, 0.5, 0.125, 0.1875, },
		lumbermill = { 0.375, 0.4375, 0.0625, 0.125, 0.5, 0.5625, 0.0625, 0.125, },
		path = "Interface\\Minimap\\POIIcons",
	}
	local t = poicons[name or "symbol"] or poicons.symbol
	if faction == "horde" then
		poicons.l, poicons.r, poicons.t, poicons.b = t[5], t[6], t[7], t[8]
	else
		poicons.l, poicons.r, poicons.t, poicons.b = t[1], t[2], t[3], t[4]
	end
	return poicons
end

--------------------------------
function Capping:GetActiveBars()
--------------------------------
	return activebars
end


local timetil
------------------------------------
function Capping:CheckStartTimer(a1)  -- timer for when a battleground begins
------------------------------------
	timetil = timetil or {
		[strlower(L["1 minute"])] = 63,
		[strlower(L["One minute until"])] = 63,
		[strlower(L["30 seconds"])] = 32,
		[strlower(L["Thirty seconds until"])] = 32,
		[strlower(L["Fifteen seconds until"])] = 16,
	}
	a1 = strlower(a1)
	for text,time in pairs(timetil) do
		if strfind(a1, text) then
			self:StartBar(L["Battleground Begins"], 63, time, "Interface\\Icons\\Spell_Holy_PrayerOfHealing", "info2")
			break
		end
	end
	if IsInArena() and strfind(a1, strlower(L["has begun"])) then  -- Shadow Sight spawn timer
		local spell, _, icon = GetSpellInfo(34709)
		self:StartBar(spell, 93, 93, icon, "info2")
	end
end

local GetBattlefieldScore, GetNumBattlefieldScores = GetBattlefieldScore, GetNumBattlefieldScores
----------------------------------------------
function Capping:GetClassByName(name, faction)  -- retrieves a player's class by name
----------------------------------------------
	for i = 1, GetNumBattlefieldScores(), 1 do
		local iname,_,_,_,_,ifaction,_,_,_,iclass,_,_ = GetBattlefieldScore(i)
		if ifaction == faction and gsub(iname, "-(.+)", "") == name then
			return iclass
		end
	end
end

local classcolor = { }
for class,color in pairs(RAID_CLASS_COLORS) do
	classcolor[class] = format("%02x%02x%02x", color.r*255, color.g*255, color.b*255)
end
--------------------------------------
function Capping:GetClassColorHex(cls)  -- retrieves hex color by class (class must be english and in caps)
--------------------------------------
	return classcolor[cls or "PRIEST"] or classcolor["PRIEST"]
end

local function CarrierOnEnter(this)
	if not this.car then return end
	local c = db.colors[strlower(this.faction)] or db.colors.info1
	this:SetBackdropColor(c.r, c.g, c.b, 0.4)
end
local function CarrierOnLeave(this)
	this:SetBackdropColor(0, 0, 0, 0)
end
---------------------------------------------------------------
function Capping:CreateCarrierButton(name, w, h, postclick, oe)  -- create common secure button
---------------------------------------------------------------
	local b = CreateFrame("Button", name, UIParent, "SecureActionButtonTemplate")
	SetWH(b, w, h)
	b:RegisterForClicks("LeftButtonUp")
	b:SetAttribute("type1", "macro")
	b:SetAttribute("macrotext", "")
	b:SetBackdrop(backdrop)
	b:SetBackdropColor(0, 0, 0, 0)
	b:SetScript("PostClick", postclick)
	b:SetScript("OnEnter", CarrierOnEnter)
	b:SetScript("OnLeave", CarrierOnLeave)
	return b
end

---------------------------------------------------------------------------------------
function Capping:CreateText(parent, fontsize, justifyH, tlrp, tlx, tly, brrp, brx, bry)  -- create common text fontstring
---------------------------------------------------------------------------------------
	local text = NewText(parent, GameFontNormal:GetFont(), fontsize, justifyH, "CENTER", "BACKGROUND")
	SetPoints(text, "TOPLEFT", tlrp, "TOPLEFT", tlx, tly, "BOTTOMRIGHT", brrp, "BOTTOMRIGHT", brx, bry)
	text:SetShadowColor(0,0,0)
	text:SetShadowOffset(1,-1)
	return text
end


do  -- initialize or update a final score estimation bar (AB and EotS uses this)
	local ascore, atime, abases, hscore, htime, hbases = 0, 0, 0, 0, 0, 0
	local updatetime, currentbg
	local f2 = L["Final: 2000 - %d"]
	local ptspersec = {
		[1] = { [0]=0.0, [1]=0.8333, [2]=1.1111, [3]=1.6667, [4]=3.3333, [5]=30.0, }, -- ab
		[2] = { [0]=0.0, [1]=0.5, [2]=1.0, [3]=2.5, [4]=5, }, -- eots
	}
	local sbpattern = {
		[1] = L["Bases: (%d+)  Resources: (%d+)/2000"],  -- ab
		[2] = L["Bases: (%d+)  Victory Points: (%d+)/2000"],  -- eots
	}
	local function getstatedata(off)
		local base, score = strmatch(select(3, GetWorldStateUIInfo(currentbg + off)) or "", sbpattern[currentbg])
		return tonumber(base), tonumber(score)
	end
	local function getlscore(time, pps, currentscore)  -- estimate loser's final score
		local lscore = time * pps + currentscore
		if currentbg == 1 then
			return format(f2, min(1990, 10 * floor((lscore + 5)/10)))
		else
			return format(f2, min(1999, floor(lscore + 0.5)))
		end
	end
	---------------------------------
	function Capping:NewEstimator(bg)  -- resets estimator and sets new battleground
	---------------------------------
		currentbg = bg
		updatetime = nil
		ascore, atime, abases, hscore, htime, hbases = 0, 0, 0, 0, 0, 0
	end
	--------------------------------------
	function Capping:UPDATE_WORLD_STATES()
	--------------------------------------
		local currenttime = GetTime()
		local ABases, AScore  = getstatedata(0)
		local HBases, HScore = getstatedata(1)
		if ABases and HBases then
			abases, hbases = ABases, HBases
			if ascore ~= AScore then
				ascore, atime, updatetime = AScore, currenttime, true
			end
			if hscore ~= HScore then
				hscore, htime, updatetime = HScore, currenttime, true
			end
		end
		
		if not updatetime then return end
		updatetime = nil

		local apps, hpps = ptspersec[currentbg][abases], ptspersec[currentbg][hbases]
		-- timeTil2000 = ((remainingScore) / scorePerSec) - (timeSinceLastUpdate)
		local ATime = ((2000 - ascore) / apps) - (currenttime - atime)
		local HTime = ((2000 - hscore) / hpps) - (currenttime - htime)
		
		local f = self:GetBar("EstFinal")
		local elapsed = (f and f:IsShown() and (f.duration - f.remaining)) or 0
		if HTime < ATime then  -- Horde is winning
			self:StartBar("EstFinal", HTime + elapsed, HTime, self:GetIconData("horde", "symbol"), "horde", nil, nil, getlscore(HTime, apps, ascore))
		else  -- Alliance is winning
			self:StartBar("EstFinal", ATime + elapsed, ATime, self:GetIconData("alliance", "symbol"), "alliance", nil, nil, getlscore(ATime, hpps, hscore))
		end
	end
end


local CappingDD
local info = { }
local abbrv = { av = av, ab = ab, eots = eots, wsg = wsg, winter = winter }
local offsetvalue, offsetcount, lastb, barid
local sbmover, cbmover, seatmover
function ShowOptions(a1, id)
	barid = type(id) == "number" and id
	if not CappingDD then
		CappingDD = CreateFrame("Frame", "CappingDD", Capping)
		CappingDD.displayMode = "MENU"
		local function UpdateLook(k)
			local texture = media:Fetch("statusbar", db.texture)
			local font = media:Fetch("font", db.font)
			local fc, bgc = db.colors.font, db.colors.bg
			self:SetWidth(db.width)
			for index, f in pairs(bars) do
				f.bar:SetTexture(texture)
				f.barback:SetTexture(texture)
				f.timetext:SetFont(font, db.fontsize-1)
				f.displaytext:SetFont(font, db.fontsize)
				f:SetHeight(db.height)
				if k == "colors" then
					local bc = db.colors[f.color]
					f.bar:SetVertexColor(bc.r, bc.g, bc.b, bc.a or 1)
					f.barback:SetVertexColor(bgc.r, bgc.g, bgc.b, bgc.a or 0.5)
					f.timetext:SetTextColor(fc.r, fc.g, fc.b, fc.a)
					f.displaytext:SetTextColor(fc.r, fc.g, fc.b, fc.a)
				end
				if k == "mainup" then f.down = not f.down end
				if k == "onegroup" and db.onegroup then f.down = not db.mainup end
				UpdateBarLayout(f)
			end
			SortBars()
			self:ModMap()
		end
		local function HideCheck(b)
			if b and b.GetName and _G[b:GetName().."Check"] then
				_G[b:GetName().."Check"]:Hide()
			end
		end
		local function CloseMenu(b) 
			if not b or not b:GetParent() then return end
			CloseDropDownMenus(b:GetParent():GetID())
		end
		hooksecurefunc("ToggleDropDownMenu", function(...) lastb = select(8, ...) end)
		local function Exec(b, k, value)
			HideCheck(b)
			if k == "showoptions" then CloseMenu(b) ShowOptions()
			elseif k == "anchor" then ToggleAnchor()
			elseif k == "syncav" and GetRealZoneText() == av then self:SyncAV()
			elseif k == "reportbg" then CloseMenu(b) ReportBG(value)
			elseif k == "reportsay" then CloseMenu(b) ReportSAY(value)
			elseif k == "enterbattle" then CloseMenu(b) ReportBG(value)
			elseif k == "leavequeue" or k == "canceltimer" then CloseMenu(b) CancelBar(value)
			elseif (k == "less" or k == "more") and lastb then
				local off = (k == "less" and -8) or 8
				if offsetvalue == value then
					offsetcount = offsetcount + off
				else
					offsetvalue, offsetcount = value, off
				end
				local tb = _G[gsub(lastb:GetName(), "ExpandArrow", "")]
				CloseMenu(b)
				ToggleDropDownMenu(b:GetParent():GetID(), tb.value, nil, nil, nil, nil, tb.menuList, tb)
			elseif k == "test" then
				local testicon = "Interface\\Icons\\Ability_ThunderBolt"
				self:StartBar(L["Test"].." - "..L["Other"].."1", 90, 90, testicon, "info1", true, true)
				self:StartBar(L["Test"].." - "..L["Other"].."2", 75, 75, testicon, "info2", true, true)
				self:StartBar(L["Test"].." - "..L["Alliance"], 45, 45, testicon, "alliance", true)
				self:StartBar(L["Test"].." - "..L["Horde"], 90, 90, testicon, "horde", true)
				self:StartBar(L["Test"], 75, 75, testicon, "info2", true)
			elseif k == "movesb" then
				sbmover = sbmover or CreateMover(nil, 220, 48, function(this)
					this:StopMovingOrSizing()
					db.sbx, db.sby = floor(this:GetLeft() + 50.5), floor(this:GetTop() - GetScreenHeight() + 10.5)
					UpdateWorldInfoPosition()
				end)
				sbmover:SetPoint("TOPLEFT", UIParent, "BOTTOMLEFT", WorldStateAlwaysUpFrame:GetLeft() - 50, WorldStateAlwaysUpFrame:GetTop() - 10)
				sbmover:Show()
			elseif k == "movecb" then
				cbmover = cbmover or CreateMover(nil, 173, 27, function(this)
					this:StopMovingOrSizing()
					db.cbx, db.cby = floor(this:GetRight() + 0.5), floor(this:GetTop() + 0.5)
					UpdateCaptureBarPosition()
				end)
				local x, y = db.cbx or max(0, MinimapCluster:GetRight() - CONTAINER_OFFSET_X), db.cby or max(20, MinimapCluster:GetBottom())
				cbmover:SetPoint("TOPRIGHT", UIParent, "BOTTOMLEFT", x, y)
				cbmover:Show()
			elseif k == "moveseat" then
				seatmover = seatmover or CreateMover(nil, 128, 128, function(this)
					this:StopMovingOrSizing()
					db.seatx, db.seaty = floor(this:GetRight() + 0.5), floor(this:GetTop() + 0.5)
					UpdateVehicleSeatPosition()
				end)
				seatmover:SetPoint("TOPRIGHT", UIParent, "BOTTOMLEFT", VehicleSeatIndicator:GetRight(), VehicleSeatIndicator:GetTop())
				seatmover:Show()
			end
		end
		local function Set(b, k)
			if not k then return end
			db[k] = not db[k]
			if abbrv[k] then  -- enable/disable a battleground while in it
				if GetRealZoneText() == abbrv[k] then
					Capping:ZONE_CHANGED_NEW_AREA()
				elseif k == "winter" and not v then
					self:StopBar(winter)
				end
			else  -- update visual options
				UpdateLook(k)
			end
		end
		local function SetSelect(b, a1)
			db[a1] = tonumber(b.value) or b.value
			local level, num = strmatch(b:GetName(), "DropDownList(%d+)Button(%d+)")
			level, num = tonumber(level) or 0, tonumber(num) or 0
			for i = 1, UIDROPDOWNMENU_MAXBUTTONS, 1 do
				local b = _G["DropDownList"..level.."Button"..i.."Check"]
				if b then
					b[i == num and "Show" or "Hide"](b)
				end
			end
			UpdateLook(a1)
		end
		local function SetColor(a1)
			local dbc = db.colors[UIDROPDOWNMENU_MENU_VALUE]
			if not dbc then return end
			if a1 then
				local pv = ColorPickerFrame.previousValues
				dbc.r, dbc.g, dbc.b, dbc.a = pv.r, pv.g, pv.b, 1 - pv.opacity
			else
				dbc.r, dbc.g, dbc.b = ColorPickerFrame:GetColorRGB()
				dbc.a = 1 - OpacitySliderFrame:GetValue()
			end
			UpdateLook("colors")
		end
		local function AddButton(lvl, text, keepshown)
			info.text = text
			info.keepShownOnClick = keepshown
			UIDropDownMenu_AddButton(info, lvl)
			wipe(info)
		end
		local function AddToggle(lvl, text, value)
			info.arg1 = value
			info.func = Set
			info.checked = db[value]
			AddButton(lvl, text, 1)
		end
		local function AddExecute(lvl, text, arg1, arg2)
			info.arg1 = arg1
			info.arg2 = arg2
			info.func = Exec
			AddButton(lvl, text, 1)
		end
		local function AddColor(lvl, text, value)
			local dbc = db.colors[value]
			if not dbc then return end
			info.hasColorSwatch = true
			info.hasOpacity = 1
			info.r, info.g, info.b, info.opacity = dbc.r, dbc.g, dbc.b, 1 - dbc.a
			info.swatchFunc, info.opacityFunc, info.cancelFunc = SetColor, SetColor, SetColor
			info.value = value
			info.func = UIDropDownMenuButton_OpenColorPicker
			AddButton(lvl, text, nil)
		end
		local function AddList(lvl, text, value)
			info.value = value
			info.hasArrow = true
			info.func = HideCheck
			AddButton(lvl, text, 1)
		end
		local function AddSelect(lvl, text, arg1, value)
			info.arg1 = arg1
			info.func = SetSelect
			info.value = value
			if tonumber(value) then
				if floor(100 * tonumber(value)) == floor(100 * tonumber(db[arg1] or -1)) then
					info.checked = true
				end
			else
				info.checked = (db[arg1] == value)
			end
			AddButton(lvl, text, 1)
		end
		local function AddFakeSlider(lvl, value, minv, maxv, step, tbl)
			local cvalue = 0
			local dbv = db[value]
			if type(dbv) == "string" and tbl then
				for i, v in ipairs(tbl) do
					if dbv == v then
						cvalue = i
						break
					end
				end
			else
				cvalue = dbv or ((maxv - minv) / 2)
			end
			local adj = (offsetvalue == value and offsetcount) or 0
			local starti = max(minv, cvalue - (7 - adj) * step)
			local endi = min(maxv, cvalue + (8 + adj) * step)
			if starti == minv then
				endi = min(maxv, starti + 16 * step)
			elseif endi == maxv then
				starti = max(minv, endi - 16 * step)
			end
			if starti > minv then
				AddExecute(lvl, "--", "less", value)
			end
			if tbl then
				for i = starti, endi, step do
					AddSelect(lvl, tbl[i], value, tbl[i])
				end
			else
				local fstring = (step >= 1 and "%d") or (step >= 0.1 and "%.1f") or "%.2f"
				for i = starti, endi, step do
					AddSelect(lvl, format(fstring, i), value, i)
				end
			end
			if endi < maxv then
				AddExecute(lvl, "++", "more", value)
			end
		end
		CappingDD.initialize = function(self, lvl)
			if lvl == 1 then
				if type(barid) == "number" then
					local bname = bars[barid].name
					info.isTitle = true
					AddButton(lvl, bname)
					if bname == winter then
						AddExecute(lvl, L["Cancel timer"], "canceltimer", barid)
					elseif not activebars[bname] then
						AddExecute(lvl, _G.ENTER_BATTLE, "enterbattle", barid)
						AddExecute(lvl, _G.LEAVE_QUEUE, "leavequeue", barid)
					else
						AddExecute(lvl, L["Send to BG"], "reportbg", barid)
						AddExecute(lvl, L["Send to SAY"], "reportsay", barid)
						AddExecute(lvl, L["Cancel timer"], "canceltimer", barid)
					end
					
					info.isTitle = true
					AddButton(lvl, " ")
					AddExecute(lvl, L["Other options"], "showoptions")
				else
					info.isTitle = true
					AddButton(lvl, "|cff5555ffCapping|r")
					AddList(lvl, _G.BATTLEFIELDS, "battlegrounds")
					AddList(lvl, L["Bar"], "bars")
					AddList(lvl, L["Battlefield Minimap"], "bgmap")
					AddList(lvl, L["Other"], "other")
					AddExecute(lvl, L["Show/Hide Anchor"], "anchor")
				end
			elseif lvl == 2 then
				local sub = UIDROPDOWNMENU_MENU_VALUE
				if sub == "battlegrounds" then
					AddList(lvl, av, "av")
					AddList(lvl, ab, "ab")
					AddList(lvl, eots, "eots")
					AddList(lvl, wsg, "wsg")
					AddList(lvl, winter, "winter")
				elseif sub == "bars" then
					AddList(lvl, L["Texture"], "texture")
					AddList(lvl, L["Width"], "width")
					AddList(lvl, L["Height"], "height")
					AddList(lvl, L["Border width"], "inset")
					AddList(lvl, L["Icons"], "iconpos")
					AddList(lvl, "Spacing", "spacing")
					AddToggle(lvl, L["Fill grow"], "fill")
					AddToggle(lvl, L["Fill right"], "reverse")
					AddToggle(lvl, L["Flip growth"], "mainup")
					AddToggle(lvl, L["Single group"], "onegroup")
					AddList(lvl, L["Font"], "font")
					AddList(lvl, _G.FONT_SIZE, "fontsize")
					AddList(lvl, L["Time position"], "timepos")
					AddList(lvl, _G.COLORS, "color")
					AddExecute(lvl, L["Test"], "test")
				elseif sub == "bgmap" then
					AddToggle(lvl, L["Narrow map mode"], "narrow")
					AddToggle(lvl, L["Narrow anchor left"], "narrowanchorleft")
					AddToggle(lvl, L["Hide border"], "hidemapborder")
					AddList(lvl, L["Map scale"], "mapscale")
				elseif sub == "other" then
					AddToggle(lvl, L["Port Timer"], "port")
					AddToggle(lvl, L["Wait Timer"], "wait")
					AddExecute(lvl, L["Reposition Scoreboard"], "movesb")
					AddExecute(lvl, L["Reposition Capture Bar"], "movecb")
					AddExecute(lvl, L["Reposition Vehicle Seat"], "moveseat")
				end
			elseif lvl == 3 then
				local sub = UIDROPDOWNMENU_MENU_VALUE
				if sub == "av" then
					AddToggle(lvl, _G.ENABLE, sub)
					AddToggle(lvl, L["Auto quest turnins"], "avquest")
					AddExecute(lvl, L["Request sync"], "syncav")
				elseif abbrv[sub] then
					AddToggle(lvl, _G.ENABLE, sub)
				elseif sub == "texture" or sub == "font" then
					local t = media:List(sub == "texture" and "statusbar" or sub)
					AddFakeSlider(lvl, sub, 1, #t, 1, t)
				elseif sub == "width" then
					AddFakeSlider(lvl, sub, 20, 600, 2, nil)
				elseif sub == "height" then
					AddFakeSlider(lvl, sub, 2, 100, 1, nil)
				elseif sub == "inset" then
					AddFakeSlider(lvl, sub, 0, 8, 1, nil)
				elseif sub == "iconpos" then
					AddSelect(lvl, L["LEFT"], sub, L["LEFT"])
					AddSelect(lvl, L["RIGHT"], sub, L["RIGHT"])
					AddSelect(lvl, L["HIDE"], sub, L["HIDE"])
				elseif sub == "spacing" then
					AddFakeSlider(lvl, sub, 0, 10, 1, nil)
				elseif sub == "fontsize" then
					AddFakeSlider(lvl, sub, 4, 28, 1, nil)
				elseif sub == "timepos" then
					AddSelect(lvl, L["LEFT"], sub, L["LEFT"])
					AddSelect(lvl, L["RIGHT"], sub, L["RIGHT"])
				elseif sub == "color" then
					AddColor(lvl, L["Alliance"], "alliance")
					AddColor(lvl, L["Horde"], "horde")
					AddColor(lvl, L["Other"].."1", "info1")
					AddColor(lvl, L["Other"].."2", "info2")
					AddColor(lvl, L["Texture"], "bg")
					AddColor(lvl, L["Font"], "font")
				elseif sub == "mapscale" then
					AddFakeSlider(lvl, sub, 0.2, 5, 0.05, nil)
				end
			end
		end
	end
	ToggleDropDownMenu(1, nil, CappingDD, "cursor")
end
